-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add client expiration check to update client before expired #20
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis pull request introduces enhancements to the client update mechanism across multiple components of the system. The changes focus on improving event parsing, client update tracking, and synchronization processes. A new Changes
Sequence DiagramsequenceDiagram
participant SyncWorker
participant EventParser
participant ClientController
participant WalletWorker
SyncWorker->>EventParser: Parse UpdateClientEvent
EventParser-->>SyncWorker: Return parsed event
SyncWorker->>ClientController: Feed update client event
ClientController-->>SyncWorker: Update client information
WalletWorker->>ClientController: Get clients to update
ClientController-->>WalletWorker: Return clients needing updates
WalletWorker->>WalletWorker: Generate update client messages
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (2)
src/workers/wallet.ts (1)
Line range hint
177-188
: Add error handling when fetching connection infoWhen retrieving connection information, if
getConnection
fails, it may cause an unhandled exception. Consider adding error handling to manage potential errors during the asynchronous operations.Apply this diff to handle possible errors:
await Promise.all( connections.map(async (connection) => { try { const connectionInfo = await ConnectionController.getConnection( this.chain.rest, this.chain.chainId, connection ) connectionClientMap[connection] = connectionInfo.client_id + } catch (error) { + this.logger.error(`Failed to get connection info for ${connection}: ${error}`) + // Optionally, handle the error or skip this connection + } }) )src/lib/eventParser.spec.ts (1)
Line range hint
1-1
: Add test coverage for parseUpdateClientEvent function.The new parseUpdateClientEvent function lacks test coverage. Consider adding test cases to verify:
- Successful parsing of update_client events
- Handling of missing fields
- Handling of malformed data
Example test structure:
test('update client parser', () => { // test successful parsing { const event = { type: 'update_client', attributes: [ { key: 'client_id', value: 'client-1' }, { key: 'header', value: 'header-data' }, { key: 'consensus_heights', value: '1-1000' } ] } const result = parseUpdateClientEvent(event) expect(result).toEqual({ clientId: 'client-1', header: 'header-data', consensusHeights: '1-1000' }) } // test missing fields { const event = { type: 'update_client', attributes: [ { key: 'client_id', value: 'client-1' } ] } expect(() => parseUpdateClientEvent(event)).toThrow() } })
🧹 Nitpick comments (3)
src/db/controller/client.spec.ts (2)
8-8
: Typo in commentThere's a duplicate word "set" in the comment. Please correct it for clarity.
Apply this diff:
mockServers // to set set config file -mockServers // to set set config file +mockServers // to set config file
41-43
: Use 'forEach' instead of 'map' for iteration with side effectsSince the result of
map
is not utilized, usingforEach
is more appropriate for performing operations with side effects.Apply this diff:
-testClients.map((client) => { +testClients.forEach((client) => { insert(DB, ClientController.tableName, client) })src/lib/eventParser.ts (1)
132-142
: LGTM: Well-structured event parsing implementation.The parseUpdateClientEvent function follows the established pattern of other event parsers in the file. It correctly extracts the required fields using the existing find helper function.
However, consider adding error handling for missing required fields.
export function parseUpdateClientEvent(event: Event): UpdateClientEvent { const clientId = find(event, 'client_id') as string const header = find(event, 'header') as string const consensusHeights = find(event, 'consensus_heights') as string + + if (!clientId || !header || !consensusHeights) { + throw new Error('Missing required fields in update client event') + } return { clientId, header, consensusHeights, } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/db/controller/client.spec.ts
(1 hunks)src/db/controller/client.ts
(3 hunks)src/lib/eventParser.spec.ts
(1 hunks)src/lib/eventParser.ts
(2 hunks)src/msgs/updateClient.ts
(3 hunks)src/workers/chain.ts
(6 hunks)src/workers/wallet.ts
(4 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/db/controller/client.ts
[error] 71-71: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
[error] 107-107: Using this in a static context can be confusing.
this refers to the class.
Unsafe fix: Use the class name instead.
(lint/complexity/noThisInStatic)
🔇 Additional comments (5)
src/db/controller/client.ts (1)
54-54
:
Add validation for parsing 'consensusHeights'
The parsing of event.consensusHeights
assumes a specific format. If the format is unexpected, it may lead to runtime errors. Consider adding validation to handle potential parsing issues.
Apply this diff to add error handling:
const consensusHeightsArray = event.consensusHeights.split(',')
const revisionHeightParts = consensusHeightsArray[0].split('-')
+if (revisionHeightParts.length < 2) {
+ throw new Error('Invalid consensusHeights format')
+}
const revisionHeight = parseInt(revisionHeightParts[1])
Likely invalid or redundant comment.
src/workers/wallet.ts (1)
306-307
: Ensure 'msgs' array is populated before proceeding
The check if (msgs.length === 0) return
is correctly placed after message generation to avoid unnecessary processing. Good job on optimizing the control flow.
src/lib/eventParser.ts (1)
2-7
: LGTM: Clean import statement update.
The import statement is properly updated to include the new UpdateClientEvent type.
src/workers/chain.ts (2)
3-8
: LGTM: Clean import updates.
The imports are properly organized to include the new UpdateClientEvent type.
269-272
: LGTM: Clean event type handling.
The update_client event type is properly handled and parsed using the new parseUpdateClientEvent function.
…for static variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (2)
src/msgs/updateClient.ts (1)
Line range hint
79-89
: Improve error handling in the header retrieval loop.The current implementation has a few issues:
- The error is only thrown after 5 retries, but the error message doesn't indicate the retry attempts.
- The delay between retries is fixed at 100ms without exponential backoff.
Consider this improved implementation:
- let header = await chain.rpc.header(height).catch(() => undefined) - let count = 0 - while (header === undefined) { - header = await chain.rpc.header(height).catch((e) => { - if (count > 5) { - throw e - } - return undefined - }) - await delay(100) - count++ - } + const MAX_RETRIES = 5; + const BASE_DELAY = 100; + let retries = 0; + + while (retries <= MAX_RETRIES) { + try { + const header = await chain.rpc.header(height); + if (header) return header; + } catch (e) { + if (retries === MAX_RETRIES) { + throw new Error(`Failed to fetch header after ${MAX_RETRIES} retries: ${e.message}`); + } + await delay(BASE_DELAY * Math.pow(2, retries)); + retries++; + } + }src/db/controller/packet.ts (1)
Line range hint
506-513
: Critical: Incorrect table names used in delete operations.Several delete operations are using the wrong table names, which will result in deleting records from the wrong tables:
- In
feedAcknowledgePacketEvent
:
- Using
tableNamePacketSend
instead oftableNamePacketTimeout
- Using
tableNamePacketSend
instead oftableNamePacketWriteAck
- In
feedTimeoutPacketEvent
:
- Using
tableNamePacketSend
instead oftableNamePacketTimeout
Apply these fixes:
- del<PacketTimeoutTable>(DB, PacketController.tableNamePacketSend, [ + del<PacketTimeoutTable>(DB, PacketController.tableNamePacketTimeout, [ - del<PacketWriteAckTable>(DB, PacketController.tableNamePacketSend, [ + del<PacketWriteAckTable>(DB, PacketController.tableNamePacketWriteAck, [ - del<PacketTimeoutTable>(DB, PacketController.tableNamePacketSend, [ + del<PacketTimeoutTable>(DB, PacketController.tableNamePacketTimeout, [Also applies to: 516-523, 570-577
🧹 Nitpick comments (4)
src/db/controller/client.ts (2)
2-2
: Organize Imports for ReadabilityConsider ordering your imports to enhance readability. Typically, external libraries are imported first, followed by internal modules.
69-82
: Avoid Magic Strings for Table NamesUsing hard-coded table names can lead to maintenance issues. Consider defining table names as constants or using configuration files.
src/db/controller/packet.ts (2)
318-325
: Consider using a transaction for atomic updates.The
resetPacketInProgress
method updates multiple tables independently. If one update fails, it could leave the system in an inconsistent state.Consider wrapping the updates in a transaction:
public static resetPacketInProgress(db?: Database) { db = db ?? DB + db.transaction(() => { update<PacketSendTable>(db, PacketController.tableNamePacketSend, { in_progress: Bool.FALSE, }) update<PacketTimeoutTable>(db, PacketController.tableNamePacketTimeout, { in_progress: Bool.FALSE, }) update<PacketWriteAckTable>(db, PacketController.tableNamePacketWriteAck, { in_progress: Bool.FALSE, }) + })() }
385-386
: Consider using a transaction for atomic inserts.The
feedSendPacketEvent
method inserts records into multiple tables independently. If one insert fails, it could leave the system in an inconsistent state.Consider wrapping the inserts in a transaction:
return () => { + DB.transaction(() => { insert(DB, PacketController.tableNamePacketSend, packetSend) insert(DB, PacketController.tableNamePacketTimeout, packetTimeout) + })()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/db/controller/channel.ts
(8 hunks)src/db/controller/client.ts
(5 hunks)src/db/controller/connection.ts
(2 hunks)src/db/controller/packet.ts
(18 hunks)src/db/controller/packetFee.ts
(2 hunks)src/db/controller/syncInfo.ts
(4 hunks)src/msgs/updateClient.ts
(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/db/controller/channel.ts
🔇 Additional comments (10)
src/db/controller/client.ts (1)
91-91
: Replace 'this' with Class Name in Static Method
In static methods, using this
can be unclear since it refers to the class itself. Replace this
with ClientController
for clarity.
Apply this diff:
- const client = selectOne<ClientTable>(DB, this.tableName, [
+ const client = selectOne<ClientTable>(DB, ClientController.tableName, [
src/db/controller/connection.ts (2)
28-28
: Replace 'this' with Class Name in Static Method
Using this
in static methods can be confusing. Replace this.tableName
with ConnectionController.tableName
for clarity.
40-49
: Consistent Reference to Class Name
Ensure consistency by replacing this.tableName
with ConnectionController.tableName
in the getConnection
method.
Apply this diff:
- const connection = selectOne<ConnectionTable>(
- DB,
- this.tableName,
+ const connection = selectOne<ConnectionTable>(
+ DB,
+ ConnectionController.tableName,
[
{
chain_id: chainId,
connection_id: connectionId,
},
]
)
src/db/controller/packetFee.ts (2)
29-29
: Replace 'this' with Class Name in Static Context
In static contexts, use the class name instead of this
for clarity. Replace this.tableName
with PacketFeeController.tableName
.
55-55
: Consistent Use of Class Name
Maintain consistency by using PacketFeeController.tableName
instead of this.tableName
.
src/db/controller/syncInfo.ts (4)
28-28
: Replace 'this' with Class Name in Static Method
Replace this.tableName
with SyncInfoController.tableName
to clarify that the reference is to the class itself.
44-44
: Consistent Reference to Class Name
For consistency, update this.tableName
to SyncInfoController.tableName
when inserting newSyncInfo
.
56-58
: Use Class Name Instead of 'this' in Static Method
In the getSyncInfos
method, replace this.tableName
with SyncInfoController.tableName
for clarity.
78-92
: Verify Logic in Sync Info Update
The update
method may not correctly handle merging sync information when syncedHeight
equals endHeight
.
Consider testing scenarios where syncedHeight
reaches endHeight
to ensure sync information merges as expected.
src/msgs/updateClient.ts (1)
118-120
: LGTM: Address comparison is now case-insensitive.
The updated implementation correctly converts both addresses to lowercase before comparison, preventing case-sensitivity issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (6)
src/db/controller/packet.spec.ts (1)
7-7
: Typo in the describe block
Please correct "controler" to "controller" for consistent naming throughout the codebase.Proposed fix:
-describe('channel controler', () => { +describe('channel controller', () => {src/lib/rpcClient.ts (1)
135-167
: Consider using a for-loop instead ofmap
for side effects.Within
rpcBlockResult.result.finalize_block_events.map((event) => { ... })
, themap
function is used for iteration rather than generating a transformed array. Switching to a for-loop improves clarity.- rpcBlockResult.result.finalize_block_events.map((event) => { + for (const event of rpcBlockResult.result.finalize_block_events) {src/db/controller/packetFee.spec.ts (1)
8-10
: Typo in the test description.Change "packet controler" to "packet controller" for consistency.
-describe('packet controler', () => { +describe('packet controller', () => {src/workers/wallet.ts (3)
Line range hint
177-189
: Add error handling for connection retrieval.The connection client mapping logic should handle potential failures when retrieving connection information.
await Promise.all( connections.map(async (connection) => { + try { const connectionInfo = await ConnectionController.getConnection( this.chain.rest, this.chain.chainId, connection ) connectionClientMap[connection] = connectionInfo.client_id + } catch (error) { + this.logger.error(`Failed to get connection info for ${connection}: ${error}`) + // Skip this connection but allow others to proceed + } }) )
243-304
: Clarify filtering comments and logic.The comments suggest filtering expired clients, but the logic is actually checking for undefined client IDs in the connection map. This could be misleading for future maintenance.
filteredSendPackets .filter( (packet) => - connectionClientMap[packet.dst_connection_id] !== undefined - ) // filter expired client + connectionClientMap[packet.dst_connection_id] !== undefined + ) // filter packets with invalid connection-client mappingSimilar changes should be applied to the filtering logic in
ackMsgs
,timeoutMsgs
, andchannelOpenMsgs
.
718-724
: Consider moving utility function to a separate file.The
asyncFilter
utility function is well-implemented but should be moved to a separate utilities file for better code organization and reusability.Consider moving it to
src/lib/utils.ts
:// src/lib/utils.ts export async function asyncFilter<T>( array: T[], filter: (v: T) => Promise<boolean> ): Promise<T[]> { const filterRes = await Promise.all(array.map((v) => filter(v))) return array.filter((v, i) => filterRes[i]) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/db/controller/client.ts
(5 hunks)src/db/controller/packet.spec.ts
(1 hunks)src/db/controller/packetFee.spec.ts
(1 hunks)src/lib/eventParser.ts
(2 hunks)src/lib/rpcClient.ts
(3 hunks)src/workers/chain.ts
(4 hunks)src/workers/wallet.ts
(7 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (28)
src/db/controller/packet.spec.ts (4)
1-4
: Imports look consistent
These imports and references to thePacketController
and utilities appear aligned with the updated packet handling.
15-29
: PacketEvent structure is correct
Thesend_packet
event data structure aligning height, sequence, connection IDs, and timeouts is appropriate for validating packet transmissions.
67-68
: Verification of test output
The final assertion correctly validates the database insertion for thesend_packet
flow. If you add more fields or logic infeedEvents
, ensure this test remains synchronized.
47-48
: Potential mismatch in destination connection
The insertion expectsdst_connection_id: 'connection-2'
, but the event'sconnectionId
is'connection-1'
. Verify whether there's logic that derives'connection-2'
or if it's a test setup mismatch.✅ Verification successful
Let me gather more context about the test file to understand the test setup.
Let me check the test setup to see how the event is constructed.
Let me check how the connection ID is derived in the packet controller.
Let me check the connection controller to understand how the counterparty connection is derived.
Let me check the ConnectionController class directly.
Now I understand the flow. Looking at the implementation in
PacketController.feedSendPacketEvent()
andConnectionController.getConnection()
, thedst_connection_id
is correctly derived from the counterparty connection information obtained through the REST client. The test is setting up a scenario whereconnection-1
on chain-1 is connected toconnection-2
on chain-2, which is a valid IBC connection setup.Test setup correctly reflects IBC connection topology
The test is valid. When a packet is sent from chain-1 usingconnection-1
, it will be received on chain-2 throughconnection-2
. The code correctly derives this by querying the connection's counterparty information through the REST client.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Searching how 'connection-2' is derived across the codebase: rg -A 5 'connection-2'Length of output: 5735
Script:
#!/bin/bash # Get the full test context around the packet insertion rg -B 10 -A 10 "dst_connection_id: 'connection-2'" src/db/controller/packet.spec.tsLength of output: 712
Script:
#!/bin/bash # Get the full test context including the event setup rg -B 50 -A 10 "dst_connection_id: 'connection-2'" src/db/controller/packet.spec.tsLength of output: 1840
Script:
#!/bin/bash # Search for the packet controller implementation ast-grep --pattern 'class PacketController { $$$ }'Length of output: 37667
Script:
#!/bin/bash # Search for the connection controller implementation ast-grep --pattern 'getConnection($$$)'Length of output: 281
Script:
#!/bin/bash # Search for the ConnectionController class ast-grep --pattern 'class ConnectionController { $$$ }'Length of output: 3056
src/lib/rpcClient.ts (2)
43-43
: Use ofdecodeBlockResults
is a solid step.Calling
decodeBlockResults(rawResponse)
helps manage block result events more cleanly. No issues observed.
195-231
: Interfaces look well structured.The new interfaces (
JsonRpcSuccessResponseGeneric
,RpcBlockResultsResponse
, etc.) improve type safety. Good job ensuring typed event attributes.src/db/controller/packetFee.spec.ts (4)
1-6
: Imports are correctly modified to reflect the shift to PacketFee.Naming and types look cleaner and align well with the fee-based events.
12-44
: Test logic aligns well with the structured event.Replacing the mock events with a single
parsePacketFeeEvent
call is sound. The attributes cover the full range of fees.
46-49
: Feed function creation is nicely modular.Calling
PacketFeeController.feedEvents
decouples the setup from actual DB writes.
55-106
: Well-defined fee expectations.Your test coverage ensures correctness for each fee type (RECV, ACK, TIMEOUT), with correct amounts. This set of checks is thorough.
src/db/controller/client.ts (6)
2-2
: Imports are consistent with the new usage ofdel
andupdate
.No concerns here.
31-34
: Client insertion logic is straightforward.Returning the inserted client is helpful for immediate usage.
Line range hint
36-66
: Refresh client withreplaceClient
is well-structured.Rechecking expired packets via
PacketController.resetPacketInProgress()
is a good approach to ensure consistent state after client replacement.
101-114
: Efficient update usage withupdate
function.The partial update pattern for
revision_height
andlast_update_time
is good.
123-123
: Lazy creation of client withthis.addClient
.This approach ensures new clients are inserted on the fly.
132-165
: Two-thirds threshold approach is acceptable.As per your prior note, approximate times are sufficient. This is well-aligned with the PR objective.
src/lib/eventParser.ts (1)
2-7
: Additional import forUpdateClientEvent
is consistent.No issues.
src/workers/chain.ts (10)
3-8
: ImportingUpdateClientEvent
is consistent with new usage.No concerns here.
14-15
: Parser functions for replace and update client events are properly referenced.Nice job ensuring all event types are accounted for.
149-154
: Combining events from multiple heights is excellent for batching.Gathers
updateClientEvents
andreplaceClientEvents
cohesively.
160-168
: Good separation of concerns for update events.
feedUpdateClientEvent
outside the DB transaction is a recommended approach to isolate potential client state issues.
169-176
: Replace client logic also stands alone.Ensures
upgradeClient
andrecoverClient
calls remain independent.
231-232
: New arrays forupdateClientEvents
andreplaceClientEvents
.Data structure is clear and consistent with the rest of the event flow.
237-246
: Flattening blockResult events is concise.Collecting
beginBlockEvents
,tx
events, andendBlockEvents
in one array is succinct.
251-253
: Initialization of arrays is well-labeled.The explicit naming (
packetFeeEvents
,updateClientEvents
, etc.) is easy to follow.
254-301
: Event extraction logic is robust.Your approach to parse each event type covers all relevant branches.
307-308
: PropertiesupdateClientEvents
andreplaceClientEvents
are cleanly returned.Enables the calling function to destructure them easily.
src/workers/wallet.ts (1)
191-194
: Verify trust period threshold in ClientController.The PR objectives mention updating clients after 2/3 of the trust period, but this threshold isn't visible in the current code. Let's verify the implementation in ClientController.
✅ Verification successful
Trust period threshold is correctly implemented at 2/3 (0.666) in ClientController
The verification confirms that the 2/3 trust period threshold is properly implemented in
src/db/controller/client.ts
:if ( client.last_update_time + client.trusting_period * 0.666 < currentTimestamp ) { return true }The code checks if:
- The client is not expired:
client.last_update_time + client.trusting_period < currentTimestamp
- The client needs update: When more than 66.6% (2/3) of the trusting period has elapsed
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check if ClientController implements the 2/3 trust period threshold # Search for the getClientsToUpdate implementation ast-grep --pattern 'getClientsToUpdate($$$) { $$$ }' # Search for any trust period calculations rg -A 5 'trust.*period|2/3|trusting.*period'Length of output: 3974
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/workers/wallet.ts (1)
204-217
:⚠️ Potential issueFix typo in function name and improve error handling.
The function name has a typo and lacks error handling for client retrieval.
-const fileterExpiredClient = async (clientId: string) => { +const filterExpiredClient = async (clientId: string) => { const currentTimestamp = new Date().valueOf() / 1000 + try { const client = await ClientController.getClient( this.chain.rest, this.chain.chainId, clientId ) return ( - client.last_update_time + client.trusting_period < currentTimestamp + Math.floor(client.last_update_time + client.trusting_period) < Math.floor(currentTimestamp) ) + } catch (error) { + this.logger.error(`Failed to get client info for ${clientId}: ${error}`) + return true // Consider client as expired if we can't verify its state + } }
🧹 Nitpick comments (4)
src/workers/wallet.ts (4)
190-194
: Add error handling for client update check.The call to
getClientsToUpdate
should include error handling to gracefully handle potential failures.- const clientsToUpdate = ClientController.getClientsToUpdate( + const clientsToUpdate = await ClientController.getClientsToUpdate( this.chain.chainId, counterpartyChainIdsWithFeeFilter.map((f) => f.chainId) - ) + ).catch(error => { + this.logger.error(`Failed to get clients to update: ${error}`); + return []; + });
243-301
: Improve code organization and type safety in message generation.The message filtering and generation code could benefit from the following improvements:
- Extract common filtering logic to reduce code duplication
- Add type safety for undefined checks
+interface PacketFilter { + connectionId: string; + clientId: string; + height: Height; +} + +function filterExpiredClientPackets<T extends { connection_id: string }>( + packets: T[], + connectionClientMap: Record<string, string>, + updateClientMsgs: Record<string, { msg: MsgUpdateClient; height: Height }> +): PacketFilter[] { + return packets + .filter(packet => { + const clientId = connectionClientMap[packet.connection_id]; + return updateClientMsgs[clientId] !== undefined; + }) + .map(packet => ({ + connectionId: packet.connection_id, + clientId: connectionClientMap[packet.connection_id], + height: updateClientMsgs[connectionClientMap[packet.connection_id]].height + })); +} // Example usage for recv packets: const recvPacketMsgs = await Promise.all( filterExpiredClientPackets( filteredSendPackets, connectionClientMap, updateClientMsgs ).map(({ height }) => this.workerController.generateRecvPacketMsg( packet, height, this.address() ) ) );
724-730
: Add documentation and error handling to asyncFilter.The helper function could benefit from JSDoc documentation and error handling for individual filter operations.
+/** + * Filters an array asynchronously using the provided filter function. + * @template T The type of elements in the array + * @param {T[]} array The array to filter + * @param {(v: T) => Promise<boolean>} filter The async filter function + * @returns {Promise<T[]>} A promise that resolves to the filtered array + */ async function asyncFilter<T>( array: T[], filter: (v: T) => Promise<boolean> ): Promise<T[]> { - const filterRes = await Promise.all(array.map((v) => filter(v))) + const filterRes = await Promise.all( + array.map((v) => + filter(v).catch((error) => { + console.error(`Filter operation failed: ${error}`); + return false; + }) + ) + ); return array.filter((v, i) => filterRes[i]) }
352-352
: Consider implementing a rate-limiting strategy.The worker processes messages in batches but doesn't implement a clear backoff strategy. Consider adding rate limiting to prevent overwhelming the chain's RPC endpoints during high load.
Some suggestions:
- Implement exponential backoff when errors occur
- Add configurable delays between batches based on chain load
- Consider using a token bucket algorithm for rate limiting
Would you like me to provide a detailed implementation for any of these approaches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/lib/restClient.ts (1)
58-62
: Consider adding robust error handling for consensus state retrieval.Currently, if the endpoint is unavailable or returns an unexpected format, the method might throw an unhandled exception. You may want to add a try/catch block or a validation step to ensure the response meets the expected schema before returning it.
src/db/controller/client.ts (1)
142-166
: Consider handling invalid or missing data infetchClient
.If
rest.ibc.getClientState(clientId)
orrest.ibc.lastConsensusState(clientId)
returns invalid data or fails unexpectedly, you risk inserting partially parsed data into the table. Consider validating or wrapping these calls in try/catch to ensure data integrity.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/db/controller/client.ts
(4 hunks)src/lib/restClient.ts
(2 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (4)
src/lib/restClient.ts (1)
113-127
: Interface definition looks consistent with IBC-Go data structures.No issues found. The design accurately reflects the consensus state fields.
src/db/controller/client.ts (3)
24-37
: Check for potential errors in client replacement flow.When replacing a client, consider validating that the
fetchClient
call succeeds. If it fails (e.g., the client doesn't exist in remote state), you could throw or handle the error to avoid inserting invalid data.
108-140
: Implementation of approximate two-thirds check looks good.The
getClientsToUpdate
method correctly filters out expired clients and returns those whoselast_update_time
meets the two-thirds threshold. This aligns with the PR objectives to update clients before they expire.
61-73
:⚠️ Potential issueAdd format validation for
event.consensusHeights
.The code implicitly assumes the string has the format
"abc,def"
and thatabc.split('-')[1]
always exists. According to the user’s learnings, ifconsensusHeights
is missing or incorrectly formatted, the process must terminate. Consider adding an explicit format check before parsing to avoid runtime errors:- const revisionHeight = parseInt(event.consensusHeights.split(',')[0].split('-')[1]) + const consensusHeightsArr = event.consensusHeights.split(',') + if (!consensusHeightsArr.length) { + throw new Error('Invalid consensusHeights format. Expected "<revision>-<height>" in a comma-separated list') + } + const [revision, height] = consensusHeightsArr[0].split('-') + if (!revision || !height) { + throw new Error('Invalid consensusHeights format. Expected "<revision>-<height>"') + } + const revisionHeight = parseInt(height, 10)Likely invalid or redundant comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/workers/wallet.ts (1)
190-203
: Ensure parallel retrievals won't exceed resource constraints.
getClientsToUpdate
and subsequent array operations might open too many parallel DB calls in large-scale scenarios. Consider adding concurrency limits or batching if performance becomes an issue.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/workers/wallet.ts
(7 hunks)
🔇 Additional comments (11)
src/workers/wallet.ts (11)
23-23
: Import for client updates is good to see.
Enables direct usage of theClientController
for retrieving and updating client data.
177-177
: Consider validating empty connections.
Ifconnections
is empty, the subsequent mapping of connection infos is skipped. Just ensure there's no undesired behavior ifconnections
is an empty array.
204-221
: Typo in function name “fileterExpiredClient”.
It’s currently misspelled as “fileterExpiredClient” and should be “filterExpiredClient”. This was flagged previously.
231-231
: Generate update client messages in a controlled loop.
While the singlePromise.all
usage here is acceptable, keep an eye on potential large concurrency issues iffilteredClientIds
grows in size.
243-259
: Avoid message generation for invalid or expired client references.
The inline filtering step correctly prevents message generation for unknown or expired clients. Good job ensuring data integrity before sending.
264-280
: Similar filtering logic present in Ack messages.
The approach matches the preceding pattern, verifying the presence of valid client update messages. This consistency is beneficial for maintainability.
285-301
: Timeout packets are similarly filtered.
Your approach ensures that only valid, unexpired clients generate timeout messages.
308-310
: Filtering channel events based on client validity.
Ensures that invalid clients do not produce channel open messages. Great consistency.
352-353
: Conditionally short-circuit withmsgs.length === 0
.
This helps prevent unnecessary transaction calls.
720-720
: Ensure defensive checks when returning filtered events.
Line 720 returns the filtered event array, which can be empty or partially pruned. It’s correct, but keep an eye on whether any remaining logic needs to handle emptiness before usage.
724-730
: UtilityasyncFilter
is well-implemented.
This helper function is straightforward and improves readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/db/controller/client.ts (2)
68-72
: ValidaterevisionHeight
parsing.
IfparseInt
fails, it returnsNaN
. Consider adding additional checks forisNaN(revisionHeight)
to avoid silently overwritingclient.revision_height
withNaN
.
81-94
: Efficient database updates.
Yourupdate<ClientTable>
approach is clear. Consider adding error handling or a rollback strategy if the update fails to ensure consistency.src/workers/wallet.ts (3)
204-218
: Expiration check logic.
Your check forclient.last_update_time + client.trusting_period > currentTimestamp
is reversed from the typical "Is it expired?" approach. Here,true
means "not expired" so the naming or logic might be confusing.
231-231
: Error handling forgenerateMsgUpdateClient
.
IfgenerateMsgUpdateClient
fails for any reason, you might end up with an unhandled situation. Consider wrapping each call in a try-catch to handle potential REST or parsing errors individually.
243-249
: Filtering for expired clients.
.filter(...) // filter expired client
might omit the reason behind the filter in code-level comments. Consider clarifying so future devs understand the condition precisely.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/db/controller/client.ts
(4 hunks)src/lib/eventParser.ts
(2 hunks)src/workers/wallet.ts
(7 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (13)
src/db/controller/client.ts (6)
2-2
: Good import organization.
Importingdel, insert, select, selectOne, update
from../utils
is consistent, and it clearly indicates the CRUD operations used throughout the file.
17-22
: Streamlined client addition logic.
UsingfetchClient
withinaddClient
simplifies the process and ensures that the client object is consistently constructed.
24-37
: Ensure error handling forreplaceClient
.
WhilereplaceClient
fetches, deletes, and reinserts the client, consider double-checking for potential race conditions or partial failures (e.g., fetch succeeds but delete fails). If partial errors occur, operations may leave inconsistent states.
75-78
: Check null fields in the header.
header.signedHeader?.header?.time.seconds
might be undefined in some edge cases. Verify upstream thatheader
is always correctly populated.
112-145
: Client filtering logic is acceptable.
The logic that excludes expired clients and includes near-expiry clients follows the approximate approach that ALPAC-4 mentioned was acceptable. Ensure that beneficial off-by-one edge cases are covered by tests.
147-171
: Code clarity for retrieval logic.
ThefetchClient
method is well-structured. It provides a single source of truth for pulling client data from REST. Good job ensuring the consistent approach forclient_id
andchain_id
.src/lib/eventParser.ts (1)
144-151
: Falling back toclient_id
.
InparseReplaceClientEvent
, falling back tosubject_client_id
orclient_id
is valid. Check that an empty string isn't returned byfind
if the attribute is missing. Throw an error if no valid client ID is found.src/workers/wallet.ts (6)
23-23
: Centralizing imports.
ImportingClientController
inwallet.ts
is consistent with the usage forclient
updates. This also clarifies dependencies among modules.
Line range hint
177-203
: Prevent concurrency issues with multiple calls.
Multiple concurrent calls toconnectionClientMap
population might need synchronization if the environment is multi-threaded or event-driven. Otherwise, partial writes could cause stale data.
219-221
: Async filter usage is neat.
asyncFilter
is a succinct way to handle concurrency. Well done simplifying the logic for validating multiple clients.
308-310
: Sorting channel close first is good.
Using.sort((a, b) => b.state - a.state)
ensures close events are processed. Confirm that this numeric ordering is stable for all channel states. For instance,CLOSE
is bigger or smaller in the enum order thanINIT
,TRYOPEN
, etc.
352-353
: Early return.
Exiting whenmsgs.length === 0
is a good step to avoid unnecessary overhead. Confirm that partial updates (e.g., some clients are updated, but no new messages are created) won't break the workflow.
720-730
:asyncFilter
is a handy utility.
TheasyncFilter
utility function is well-implemented; it systematically checks each item in parallel. This is efficient and shortens code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/db/controller/client.ts (4)
17-22
: Check for database insertion errors.
While this insertion is straightforward, consider wrappinginsert
in a try-catch block or verifying that the database call succeeds to handle potential DB errors more gracefully.
24-37
: Ensure robust error handling in replaceClient.
When deleting and re-inserting the client, if either DB call fails, it could corrupt or partially remove the record. Consider transactional operations or a rollback strategy to maintain DB consistency.
81-94
: Consider logging or returning update success status.
Right now, the function callsupdate
silently. Providing some form of acknowledgment or logging would improve observability to confirm a successful DB update.
103-103
: Handle potential fetchClient errors.
This usage is fine, but keep in mind thatthis.addClient(...)
could fail if it can’t fetch or insert. A try-catch approach here could stop repetitive DB attempts in infinite loops if rest calls fail.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/db/controller/client.ts
(4 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (7)
src/db/controller/client.ts (7)
2-2
: Import usage looks good.
Importingdel, insert, select, selectOne, update
centralizes CRUD operations and promotes consistency.
7-7
: PacketController import is appropriate.
Bringing inPacketController
precisely where needed keeps dependencies uncluttered.
61-66
: Consensus heights format validation is correct.
This new check effectively guards against invalidconsensusHeights
strings and aligns with the IBC-Go spec requirement of anumber-number
format.
70-72
: Revision height logic seems appropriate.
Only updating whenrevisionHeight
exceeds the currentclient.revision_height
is correct. This ensures monotonic revision progression.
75-78
: Time comparison is accurate.
Usingheader.signedHeader.header.time.seconds
ensures we only update if there's a strictly newer timestamp. This is aligned with typical block timestamp validation.
113-145
: Approximate timing logic is acceptable.
As per your clarifications, the approximate check at two-thirds of the trusting period is sufficient for the use case, ensuring timely updates without being overly strict.
147-171
: Successful integration of fetchClient method.
Centralizing client data retrieval infetchClient
fosters reusability. The code is clean, and the usage ofstate
plusconsState
is well-organized.
Features
feedUpdateClientEvent
function.Improvements
rpc.header
instead ofrest.block_info
to reduce query costs and avoid thegrpc: received message larger than max
error.Summary by CodeRabbit
New Features
UpdateClientEvent
in event processing.Bug Fixes
getValidatorSet
function.Tests
ClientController
functionality.parsePacketEvent
function to align with new specifications.Chores